sast: Phase 1 triage — fix Semgrep rule scope, dismiss 47 alerts#152
Merged
sast: Phase 1 triage — fix Semgrep rule scope, dismiss 47 alerts#152
Conversation
The generic-language security-definer rule was matching SECURITY DEFINER occurrences in plans/sql/*.md design documents because Semgrep's path matching treats 'sql/**' as a substring match that covers 'plans/sql/'. Add explicit excludes for **/*.md, plans/**, and docs/** so the rule only fires on actual SQL and Rust source files. Also dismiss all 47 open Security tab alerts: - 2 CodeQL CWE-312 (used in tests: synthetic salary data in e2e fixtures) - 13 Semgrep security-definer (false positive: docs/plans markdown files) - 32 Semgrep spi.run/query.dynamic-format (false positive: triaged 2026-03-10; all interpolated values are pre-quoted catalog identifiers, OIDs, or internal temp table names — none reachable from user input)
- Mark Phase 0 as complete with actual CI outcomes (CodeQL 0 findings, deny clean, 47 Semgrep alerts triaged) - Mark Phase 1 as complete with list of completed tasks - Update Implementation Checklist: restructure into Phase 0 done / Phase 1 done / Phase 2 next / Phase 3 pending - Update Phase completion criteria table with checkmarks - Add Triage Log section documenting all 47 alert dismissals: - 2 CodeQL CWE-312 false positives (salary in test fixtures) - 13 Semgrep security-definer hits in plan/doc markdown files - 32 Semgrep SPI dynamic-format hits (all pre-quoted, not user input) - Update Table of Contents - Replace stale Recommendation text with Phase 2 roadmap
Phase 2 — Extension-specific privilege-context Semgrep rules:
- sql.row-security.disabled: flag SET LOCAL row_security = off
- sql.set-role.present: flag SET ROLE / RESET ROLE
- Update sql.security-definer.present message with SET search_path guidance
(search-path hijacking risk explicitly called out for reviewers)
- All three rules advisory (WARNING/INFO), scoped to src/** + sql/**,
excluding *.md / plans/** / docs/**
Phase 3 — Unsafe block inventory:
- scripts/unsafe_inventory.sh: per-file grep-based unsafe { counter
with --report-only and --update modes; writes GITHUB_STEP_SUMMARY in CI
- .unsafe-baseline: committed baseline (1309 blocks across 6 files)
- .github/workflows/unsafe-inventory.yml: advisory CI workflow that runs
on PRs touching src/**; fails if any file exceeds its baseline count
- just unsafe-inventory recipe for local use
Docs:
- plans/testing/PLAN_SAST.md: Phase 2 and 3 marked complete
- CHANGELOG.md: Phase 2+3 entries added
- ROADMAP.md: SAST section added tracking all S1-S8 items
.unwrap(), .expect(), and panic!() abort the PostgreSQL backend process
if reached from a SQL-callable function. The new advisory rule surfaces
all ~37 existing callsites in src/** for triage.
Known patterns in the existing hits:
- expect("unreachable after error!()") — valid pgrx idiom post-error!()
- expect() inside #[cfg(test)] mod tests blocks (recursive_cte.rs:3270+)
- a handful of genuine candidates in api.rs and parser.rs
All hits are advisory; none block CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SAST Phases 1–3 + additional Phase 2 rule: alert triage, CI tuning, privilege-context rules, panic rule, and unsafe inventory.
Changes
Phase 1 — Alert triage
sql.security-definer.presentto excludeplans/**/docs/**/**/*.mdCI trigger tightening
pull_requesttrigger from CodeQL and Semgrep workflowsPhase 2 — Extension-specific Semgrep rules
sql.row-security.disabled: flagSET LOCAL row_security = offsql.set-role.present: flagSET ROLE/RESET ROLErust.panic-in-sql-path: flag.unwrap(),.expect(),panic!()insrc/**— panics abort the PostgreSQL backend process; ~37 hits triaged, mostly known-safe idiomssql.security-definer.presentmessage updated with explicitSET search_pathguidancePhase 3 — Unsafe growth controls
scripts/unsafe_inventory.sh: per-fileunsafe {counter.unsafe-baseline: committed baseline (1309 blocks across 6 files).github/work-.github/work-.github/work-.github/work-.github/work-.github/work-.githuben-.github/work-.github/work-.github/work-.github/worunt-.github/work-.gitn | |-------|------|------|----------| | 2 | CodeQL | CWE-312 cleartext logging/storage |used in tests— synthetic salary dat| 2 | CodeQL | CWE-312 cleartext logging/storag-definer.present|false positive— markdown design docs; rule fixed || 32 | Semgrep |
spi.run/query.dynamic-format|false positive— all pre-quoted identifiers, OIDs, or internal names |Semgrep rules (6 total — all advisory)
rust.spi.run.dynamic-formatSpi::runrust.spi.query.dynamic-formatSpi::get_*sql.security-definer.presentSECURITY DEFINERreview +search_pathguidancesql.row-security.disabledSET LOCAL row_security = offsql.set-role.presentSET ROLE/RESET ROLErust.panic-in-sql-path.unwrap()/.expect()/panic!()insrc/**What's next (Phase 4)
Promote high-confidence rules to blocking after v0.3.0 RLS/privilege-hardeningPromote high-confidence rules to -context rules.